﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Legend.Guarding;

namespace Legend.Core.Tests.Guarding
{
    [TestFixture]
    public class GuardingExtensionMethodsTests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsNotNull_throws_exception_when_argument_is_null()
        {
            string value = null;
            Require.ThatArgument(value).IsNotNull();
        }

        [Test]
        public void IsNotNull_does_not_throw_exception_when_argument_is_not_null()
        {
            string value = "foo";
            Require.ThatArgument(value).IsNotNull();
        }

        [Test]
        public void IsNotNull_throws_exception_with_argument_name_set_when_name_is_specified()
        {
            string value = null;
            
            AssertThrows<ArgumentNullException>(() =>
                {
                    Require.ThatArgument(value).Named("value").IsNotNull();
                }, x => x.ParamName == "value");
        }

        [Test]
        public void IsNotNull_throws_exception_when_ArgumentInfo_is_null()
        {
            AssertThrows<ArgumentNullException>(() =>
                {
                    GuardingExtensionMethods.IsNotNull((ArgumentInfo<string>)null);
                });
        }

        [Test]
        public void IsNotNull_returns_accessor_that_accesses_the_same_ArgumentInfo()
        {
            var info = Require.ThatArgument("foo");
            var accessor = info.IsNotNull();

            Assert.AreEqual(info, accessor.And);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public virtual void IsNotNull_throws_when_nullable_type_is_null()
        {
            int? argument = null;
            Require.ThatArgument(argument).IsNotNull();
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public virtual void IsNotNull_with_nullable_type_throws_when_argument_info_is_null()
        {
            ArgumentInfo<int?> info = null;
            GuardingExtensionMethods.IsNotNull(info);
        }

        [Test]
        public virtual void IsNotNull_with_nullable_type_returns_not_nullable_value()
        {
            int? arg = 10;
            IArgumentInfo<int> returned = Require.ThatArgument(arg).IsNotNull().And;
            Assert.AreEqual(10, returned.Value);
        }

        [Test]
        public void IsNotNullOrEmpty_throws_when_string_is_null()
        {
            string value = null;
            
            AssertThrows<ArgumentNullException>(() => 
                {
                    Require.ThatArgument(value).IsNotNullOrEmpty();
                });
        }

        [Test]
        public void IsNotNullOrEmpty_throws_when_string_is_empty()
        {
            string value = string.Empty;
            AssertThrows<ArgumentNullException>(() => {
                Require.ThatArgument(value).IsNotNullOrEmpty();
            });
        }

        [Test]
        public void IsNotNullOrEmpty_throws_when_argument_info_is_null()
        {
            AssertThrows<ArgumentNullException>(() => 
                {
                    GuardingExtensionMethods.IsNotNullOrEmpty(null);
                });
        }

        [Test]
        public void IsNotNullOrEmpty_does_not_throw_when_string_is_not_null_or_empty()
        {
            string value = "foo";
            Require.ThatArgument(value).IsNotNullOrEmpty();
        }

        [Test]
        public void IsNotNullOrEmpty_has_argument_name_set_in_exception_when_specified()
        {
            string value = null;

            AssertThrows<ArgumentNullException>(() => {
                Require.ThatArgument(value).Named("value").IsNotNullOrEmpty();    
            }, x => x.ParamName == "value");
            
        }

        [Test]
        public void IsNotNullOrEmpty_returns_accessor_that_accesses_the_same_ArgumentInfo()
        {
            var info = Require.ThatArgument("foo");
            var accessor = info.IsNotNullOrEmpty();

            Assert.AreEqual(info, accessor.And);
        }

        [Test]
        public void IsInRange_throws_when_value_is_lower_than_lower_bound()
        {
            AssertThrows<ArgumentOutOfRangeException>(() => {
                Require.ThatArgument(0).IsInRange(1, 2);
            });
        }

        [Test]
        public void IsInRange_throws_when_value_is_over_upper_bound()
        {
            AssertThrows<ArgumentOutOfRangeException>(() => {
                Require.ThatArgument(3).IsInRange(1, 2);
            });
        }

        [Test]
        public void IsInRange_throws_when_argument_info_is_null()
        {
            AssertThrows<ArgumentNullException>(() => 
                {
                    GuardingExtensionMethods.IsInRange(null, 0, 1);
                });
        }

        [Test]
        public void IsInRange_doesnt_throw_when_value_is_on_lower_bound()
        {
            Require.ThatArgument(1).IsInRange(1, 2);
        }

        [Test]
        public void IsInRange_doesnt_throw_when_value_is_on_upper_bound()
        {
            Require.ThatArgument(2).IsInRange(1, 2);
        }

        [Test]
        public void IsInRange_doesnt_throw_when_value_is_between_bounds()
        {
            Require.ThatArgument(2).IsInRange(1, 3);
        }

        [Test]
        public void IsInRange_sets_argument_name_to_exception_when_specified()
        {
            AssertThrows<ArgumentOutOfRangeException>(() => {
                Require.ThatArgument(1).Named("value").IsInRange(2, 3);
            }, x => x.ParamName  == "value");
        }

        [Test]
        public void IsInRange_returns_accessor_that_accesses_the_same_ArgumentInfo()
        {
            var info = Require.ThatArgument(1);
            var accessor = info.IsInRange(0, 2);
            
            Assert.AreEqual(info, accessor.And);
        }

        // -----
        private static void AssertThrows<T>(Action block) where T : Exception
        {
            AssertThrows<T>(block, x => true);
        }

        private static void AssertThrows<T>(Action block, Func<T, bool> exceptionEvaluation) where T : Exception
        {
            try
            {
                block();
            }
            catch (T ex)
            {
                if (exceptionEvaluation(ex))
                    return;
            }

            Assert.Fail("Expected exception was not thrown.");
        }
    }
}